/*
 * 我们将在这里学习一些单元测试。如果您真的不知道单元测试是什么，请先学习这个主题。
 * 集成测试在技术上与 Orchard 服务非常相似，只是我们不是只测试单个服务，而是在混合中添加一些更多的实际实现。
 *
 * 首先，我们将在这里创建一个服务，然后稍后在测试项目中测试它。此服务不会在其他地方使用，
 * 它只是一个要测试的示例。
 *
 * 为什么是服务？服务通常是基于 Orchard 的 Web 应用程序中大部分复杂逻辑所在的地方。您可以测试任何内容，
 * 只要您以可测试的方式编写它（例如，不利用隐藏的依赖项，而是注入所有依赖项），您可以为控制器、驱动程序、
 * 后台任务等编写测试。但是，我们认为除非您目标是 100% 测试覆盖率，否则最好将单元和集成测试工作集中在服务上。
 * 然后，应用程序的其余部分可以通过例如 UI 测试进一步自动测试。
 */

using OrchardCore.ContentManagement;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;

namespace Ruitu.Edu.TrainingDemo.Services;

/// <summary>
/// 测试服务接口 - 用于演示单元测试的服务接口
/// 
/// 主要用途：
/// - 演示如何设计可测试的服务接口
/// - 展示依赖注入的最佳实践
/// - 提供单元测试的示例服务
/// 
/// 使用场景：
/// - 需要创建可测试的服务
/// - 需要演示单元测试模式
/// - 需要展示异常处理
/// 
/// 设计原因：
/// - 遵循依赖注入原则
/// - 提供清晰的接口定义
/// - 支持测试驱动开发
/// 
/// 设计模式：
/// - 接口模式：通过接口实现解耦
/// - 依赖注入模式：支持服务注入
/// - 工厂模式：通过服务容器创建实例
/// </summary>
// 首先我们声明我们服务的接口（就像我们之前对 IDateTimeCachingService 所做的那样）。这样
// 使用服务的其他类将能够注入它并依赖接口而不是实现，使它们也可测试！
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Just a simple sample.")]
public interface ITestedService
{
    /// <summary>
    /// 获取内容项或在找不到时抛出异常
    /// </summary>
    /// <param name="id">内容项 ID</param>
    /// <returns>内容项</returns>
    /// <exception cref="ArgumentNullException">当 ID 为 null 或空时抛出</exception>
    /// <exception cref="InvalidOperationException">当内容项不存在时抛出</exception>
    // 只是一个简单的示例方法，它将检索内容项或在找不到时抛出异常。
    Task<ContentItem> GetContentItemOrThrowAsync(string id);
}

/// <summary>
/// 测试服务实现 - 演示可测试服务的实现
/// </summary>
public class TestedService : ITestedService
{
    private readonly IContentManager _contentManager;

    /// <summary>
    /// 初始化测试服务的新实例
    /// </summary>
    /// <param name="contentManager">内容管理器</param>
    public TestedService(IContentManager contentManager) => _contentManager = contentManager;

    /// <summary>
    /// 获取内容项或在找不到时抛出异常
    /// </summary>
    /// <param name="id">内容项 ID</param>
    /// <returns>内容项</returns>
    /// <exception cref="ArgumentNullException">当 ID 为 null 或空时抛出</exception>
    /// <exception cref="InvalidOperationException">当内容项不存在时抛出</exception>
    public Task<ContentItem> GetContentItemOrThrowAsync(string id)
    {
        // 如您所见，我们严格检查输入。这是我们稍后肯定需要测试的内容！
        if (string.IsNullOrEmpty(id))
        {
            throw new ArgumentNullException(nameof(id), "The supplied content item ID was null or empty.");
        }

        // 这被分解出来以遵守这里的建议：https://rules.sonarsource.com/csharp/RSPEC-4457
        return GetContentItemOrThrowInternalAsync(id);
    }

    /// <summary>
    /// 内部方法：获取内容项或在找不到时抛出异常
    /// </summary>
    /// <param name="id">内容项 ID</param>
    /// <returns>内容项</returns>
    /// <exception cref="InvalidOperationException">当内容项不存在时抛出</exception>
    private async Task<ContentItem> GetContentItemOrThrowInternalAsync(string id) =>
        // 您已经知道这是如何工作的：）。
        await _contentManager.GetAsync(id)
            ?? throw new InvalidOperationException($"The content item with the ID {id} doesn't exist.");
}

// 下一站：现在我们有了服务，让我们为它编写一些测试！这些将在单独的项目中，
// 因为通常您不想混合测试和生产代码。所以前往 Ruitu.Edu.TrainingDemo.Tests 项目！
